package com.zcyppyh.ddns.service;

import cn.hutool.core.date.DateUtil;
import com.aliyun.alidns20150109.Client;
import com.aliyun.alidns20150109.models.*;
import com.aliyun.teautil.models.RuntimeOptions;
import com.zcyppyh.ddns.config.ProjectConfig;
import com.zcyppyh.ddns.config.Record;
import com.zcyppyh.ddns.enums.ProtocolTypeEnum;
import com.zcyppyh.ddns.host.PublicHost;
import com.zcyppyh.ddns.utils.ProjectUtil;
import com.zcyppyh.ddns.utils.SpringBeanUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.util.StringUtils;

import java.util.*;

public class Dns {

    private static Logger logger = LoggerFactory.getLogger(Dns.class);

    public static final String IPV4 = "ipv4";
    public static final String IPV6 = "ipv6";

    public static String IPV4_URL = "";
    public static String IPV6_URL = "";

    public void execute() throws Exception {
        logger.info("------------当前时间 {} 开始进行本轮解析------------", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        //获取阿里云客户端
        Client client = ProjectUtil.createClient();
        //获取本地解析配置
        ProjectConfig projectConfig = ProjectUtil.getProjectConfig();
        String domainName = projectConfig.getDomain();
        List<Record> recordList = projectConfig.getRecordList();
        if(recordList.isEmpty()){
            logger.warn("没有配置解析配置");
            return;
        }

        //获取阿里云解析记录
        Map<String, DescribeDomainRecordsResponseBody.DescribeDomainRecordsResponseBodyDomainRecordsRecord> recordMap = describeDomainRecords(client, domainName);
        for (Record record: recordList) {
            logger.info("++++开始解析记录[{}]++++", record.getRR());
            record.setValue(record.getType().equals("AAAA")?getIpv6():getIpv4());
            logger.info("公网IP地址为：{}", record.getValue());
            if(!StringUtils.hasText(record.getValue())){
                continue;
            }
            if(!recordMap.containsKey(record.getRR())){
                try {
                    //添加解析记录
                    addDomainRecord(client, domainName, record);
                } catch (Exception e){}
            }else{
                DescribeDomainRecordsResponseBody.DescribeDomainRecordsResponseBodyDomainRecordsRecord remoteRecord = recordMap.get(record.getRR());
                if(record.getValue().equals(remoteRecord.getValue())){
                    logger.info("IP地址没有变化");
                    continue;
                }
                record.setRecordId(remoteRecord.getRecordId());
                try {
                    //更新解析记录
                    updateDomainRecord(client, record);
                }catch (Exception e){}
            }
            logger.info("++++本次处理完成++++");
        }
        logger.info("------------当前时间 {} 本轮解析完成------------", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
    }

    /**
     * 获取域名解析记录
     * @param client
     * @param domainName
     * @return
     * @throws Exception
     */
    public Map<String, DescribeDomainRecordsResponseBody.DescribeDomainRecordsResponseBodyDomainRecordsRecord> describeDomainRecords(Client client, String domainName) throws Exception {
        logger.info("开始获取【{}】的解析记录", domainName);
        DescribeDomainRecordsRequest request = new DescribeDomainRecordsRequest();
        request.setDomainName(domainName);
        RuntimeOptions runtimeOptions = new RuntimeOptions();
        try {
            DescribeDomainRecordsResponse response = client.describeDomainRecordsWithOptions(request, runtimeOptions);
            Integer statusCode = response.getStatusCode();
            if(statusCode==200){
                logger.info("获取【{}】的解析记录成功", domainName);
                List<DescribeDomainRecordsResponseBody.DescribeDomainRecordsResponseBodyDomainRecordsRecord> recordList = response.getBody().getDomainRecords().getRecord();
                Map<String, DescribeDomainRecordsResponseBody.DescribeDomainRecordsResponseBodyDomainRecordsRecord> recordMap = new HashMap<>();
                for (int i = 0; i < recordList.size(); i++) {
                    Record record = new Record();
                    BeanUtils.copyProperties(recordList.get(i), record);
                    recordMap.put(record.getRR(), recordList.get(i));
                }
                return recordMap;
            }else{
                logger.info("获取【{}】的解析记录时失败，响应状态为[{}]", domainName, statusCode);
                throw new Exception("获取解析记录失败");
            }
        } catch (Exception e) {
            logger.error("获取【{}】的解析记录时出错", domainName, e);
            throw e;
        }
    }

    /**
     * 更新解析记录
     * @param client
     * @param record
     * @throws Exception
     */
    public void updateDomainRecord(Client client, Record record) throws Exception {
        logger.info("开始更新主机记录为[{}]，记录类型为[{}]的解析记录", record.getRR(), record.getType());
        UpdateDomainRecordRequest request = new UpdateDomainRecordRequest();
        request.setRecordId(record.getRecordId())
                .setRR(record.getRR())
                .setType(record.getType())
                .setValue(record.getValue());
        RuntimeOptions runtimeOptions = new RuntimeOptions();
        try {
            UpdateDomainRecordResponse response = client.updateDomainRecordWithOptions(request, runtimeOptions);
            Integer statusCode = response.getStatusCode();
            if(statusCode==200){
                logger.info("主机记录为[{}]，记录类型为[{}]的解析记录更新成功", record.getRR(), record.getType());
            }else{
                logger.info("主机记录为[{}]，记录类型为[{}]的解析记录更新失败，响应状态为[{}]", record.getRR(), record.getType(), statusCode);
                throw new Exception("更新解析记录失败");
            }
        } catch (Exception e) {
            logger.info("主机记录为[{}]，记录类型为[{}]的解析记录更新失败", record.getRR(), record.getType(), e);
            throw e;
        }
    }

    /**
     * 添加解析记录
     * @param client
     * @param record
     * @throws Exception
     */
    public void addDomainRecord(Client client, String domainName, Record record) throws Exception {
        logger.info("添加主机记录为[{}]，记录类型为[{}]的解析记录", record.getRR(), record.getType());
        AddDomainRecordRequest request = new AddDomainRecordRequest();
        request.setDomainName(domainName)
                .setRR(record.getRR())
                .setType(record.getType())
                .setValue(record.getValue());
        RuntimeOptions runtimeOptions = new RuntimeOptions();
        try {
            AddDomainRecordResponse response = client.addDomainRecordWithOptions(request, runtimeOptions);
            Integer statusCode = response.getStatusCode();
            if(statusCode==200){
                logger.info("主机记录为[{}]，记录类型为[{}]的解析记录添加成功", record.getRR(), record.getType());
            }else{
                logger.info("主机记录为[{}]，记录类型为[{}]的解析记录添加失败，响应状态为[{}]", record.getRR(), record.getType(), statusCode);
                throw new Exception("更新解析记录失败");
            }
        } catch (Exception e) {
            logger.info("主机记录为[{}]，记录类型为[{}]的解析记录添加失败", record.getRR(), record.getType(), e);
            throw e;
        }
    }

    /**
     * 获取公网ipv4地址
     * @return
     */
    public String getIpv4(){
        logger.info("开始获取公网ipv4地址");
        if(StringUtils.hasText(IPV4_URL)){
            return IPV4_URL;
        }
        Map<String, PublicHost> beans = SpringBeanUtil.getBeans(PublicHost.class);
        if(beans.isEmpty()){
            return IPV4_URL;
        }
        List<PublicHost> systemImplList = new ArrayList<>();
        for (String key : beans.keySet()) {
            PublicHost publicHost = beans.get(key);
            if(ProtocolTypeEnum.IPV4.equals(publicHost.protocolType())){
                if(!publicHost.isCustImpl()){
                    systemImplList.add(publicHost);
                    continue;
                }
                try {
                    String publicIp = publicHost.getPublicIp();
                    if(StringUtils.hasText(publicIp)){
                        IPV4_URL = publicIp;
                        logger.info("获取公网ipv4地址成功");
                        break;
                    }
                }catch (Exception e){
                    logger.warn("{}获取公网ipv4地址失败", publicHost.getClass().toGenericString(), e);
                }
            }
        }
        if(!StringUtils.hasText(IPV4_URL)){
            for (int i = 0; i < systemImplList.size(); i++) {
                PublicHost publicHost = systemImplList.get(i);
                try {
                    String publicIp = publicHost.getPublicIp();
                    if(StringUtils.hasText(publicIp)){
                        IPV4_URL = publicIp;
                        logger.info("获取公网ipv4地址成功");
                        break;
                    }
                }catch (Exception e){
                    logger.warn("{}获取公网ipv4地址失败", publicHost.getClass().toGenericString(), e);
                }
            }
        }
        return IPV4_URL;
    }

    /**
     * 获取公网ipv6地址
     * @return
     */
    public String getIpv6(){
        logger.info("开始获取公网ipv6地址");
        if(StringUtils.hasText(IPV6_URL)){
            return IPV6_URL;
        }
        Map<String, PublicHost> beans = SpringBeanUtil.getBeans(PublicHost.class);
        if(beans.isEmpty()){
            return IPV6_URL;
        }
        List<PublicHost> systemImplList = new ArrayList<>();
        for (String key : beans.keySet()) {
            PublicHost publicHost = beans.get(key);
            if(ProtocolTypeEnum.IPV6.equals(publicHost.protocolType())){
                if(!publicHost.isCustImpl()){
                    systemImplList.add(publicHost);
                    continue;
                }
                try {
                    String publicIp = publicHost.getPublicIp();
                    if(StringUtils.hasText(publicIp)){
                        IPV6_URL = publicIp;
                        logger.info("获取公网ipv6地址成功");
                        break;
                    }
                }catch (Exception e){
                    logger.warn("{}获取公网ipv6地址失败", publicHost.getClass().toGenericString(), e);
                }
            }
        }
        if(!StringUtils.hasText(IPV6_URL)){
            for (int i = 0; i < systemImplList.size(); i++) {
                PublicHost publicHost = systemImplList.get(i);
                try {
                    String publicIp = publicHost.getPublicIp();
                    if(StringUtils.hasText(publicIp)){
                        IPV6_URL = publicIp;
                        logger.info("获取公网ipv6地址成功");
                        break;
                    }
                }catch (Exception e){
                    logger.warn("{}获取公网ipv6地址失败", publicHost.getClass().toGenericString(), e);
                }
            }
        }
        return IPV6_URL;
    }
}
